package com.tiancheng.trade.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tiancheng.trade.commom.core.data.Result;
import com.tiancheng.trade.commom.core.enums.PaySceneEnum;
import com.tiancheng.trade.commom.core.enums.PaymentChannelEnum;
import com.tiancheng.trade.commom.core.enums.PaymentWayEnum;
import com.tiancheng.trade.commom.core.exception.BusinessException;
import com.tiancheng.trade.commom.core.exception.error.BusinessErrorInfoEnum;
import com.tiancheng.trade.commom.web.model.response.PaymentPayWayQueryResponse;
import com.tiancheng.trade.commom.web.model.response.UnifiedOrderResVO;
import com.tiancheng.trade.order.bo.PayParamsCacheBO;
import com.tiancheng.trade.order.enums.*;
import com.tiancheng.trade.commom.web.model.request.OrderPayDTO;
import com.tiancheng.trade.order.mapper.OrderMapper;
import com.tiancheng.trade.order.model.Coupon;
import com.tiancheng.trade.order.model.Order;
import com.tiancheng.trade.order.model.OrderBatch;
import com.tiancheng.trade.order.model.SubOrder;
import com.tiancheng.trade.order.service.CouponService;
import com.tiancheng.trade.order.service.OrderBatchPayService;
import com.tiancheng.trade.order.service.OrderBatchService;
import com.tiancheng.trade.order.util.EnumUtil;
import com.tiancheng.trade.order.util.ObjectConvertUtil;
import com.tiancheng.trade.order.util.cache.OrderCache;
import com.tiancheng.trade.order.util.lock.Lock;
import com.tiancheng.trade.order.vo.app.CashierVO;
import com.tiancheng.trade.order.vo.app.res.OrderPrePayResVO;
import com.tiancheng.trade.order.vo.open.order.OpenCashierResVO;
import com.tiancheng.trade.order.vo.open.order.OpenOrderPayVO;
import com.tiancheng.trade.order.vo.parallel.CashierParallelVO;
import com.tiancheng.trade.order.vo.parallel.OrderPayParallelVO;
import com.tiancheng.trade.order.vo.parallel.res.OrderPrePayParallelResVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: likailun
 * @Description: 批量订单付款service 实现
 * @Date: create in 2023/6/9 16:55
 */
@Slf4j
@Service
public class OrderBatchPayServiceImpl extends BaseOrderPayService implements OrderBatchPayService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private CouponService couponService;
    @Resource
    private OrderBatchService orderBatchService;


    @Override
    public OpenCashierResVO initCashier(CashierParallelVO cashierParallelVO) {
        CashierVO cashierVO = new CashierVO();
        cashierVO.setOrderIds(cashierParallelVO.getOrderIds());
        cashierVO.setAppType(cashierParallelVO.getAppType());
        cashierVO.setAppVersion(cashierParallelVO.getAppVersion());

        final long batchCount = Arrays.stream(cashierVO.getOrderIds()).filter(o -> o.startsWith("B")).distinct().count();
        BusinessErrorInfoEnum.ORDER_CASHIER_MERGE_NOT_SUPPORT.assertIsTrue(cashierVO.getOrderIds().length == 1 && batchCount == 1);
        QueryWrapper<Order> conditions = new QueryWrapper<>();
        conditions.eq("uid", cashierParallelVO.getUid());
        conditions.eq("batch_id", cashierVO.getOrderIds()[0]);
        //conditions.eq("order_status", OrderStatusEnum.waiting_pay.getCode());
        final List<Order> orders = orderMapper.selectList(conditions);
        BusinessErrorInfoEnum.ORDER_CASHIER_STATUS.assertIsTrue(orders.size() > 0);
        final Optional<Order> any = orders.stream().filter(o -> !o.getOrderStatus().equals(OrderStatusEnum.waiting_pay.getCode())).findAny();
        if (any.isPresent()) {
            // 有订单状态不是WP的，报错
            BusinessErrorInfoEnum.ORDER_CASHIER_STATUS.assertFail();
        }
        // 获取订单的支付渠道，并判断禁止跨渠道支付，以及判断订单是否支付超时
        Long orgPaymentChannelId = orders.get(0).getOrgPaymentChannelId();
        for (Order order : orders) {
            BusinessErrorInfoEnum.ORDER_CASHIER_CHANNEL_DIFF.assertIsTrue(orgPaymentChannelId.equals(order.getOrgPaymentChannelId()));
            BusinessErrorInfoEnum.ORDER_PAY_TIMEOUT_CLOSE.assertIsTrue(order.getPayExpiryTime().isAfter(LocalDateTime.now()));
        }

        // 判断优惠券，目前只允许带一张优惠券支付，如果是合单支付，不允许带优惠券
        List<Coupon> coupons = couponService.find(orders.stream().map(Order::getOrderId).toArray(String[]::new));
//        BusinessErrorInfoEnum.ORDER_CASHIER_COUPON_ONE.assertIsTrue(coupons.size() <= 1);
//        BusinessErrorInfoEnum.ORDER_CASHIER_MERGE_COUPON.assertIsTrue(orders.size() == 1 || coupons.size() == 0);

        OpenCashierResVO cashierResVO = new OpenCashierResVO();
        cashierResVO.setOrderSource(orders.size() > 1 ? OrderSourceEnum.CONSOLIDATION.getCode() : orders.get(0).getOrderSource());
        cashierResVO.setType(EnumUtil.getEnumByCode(OrderSourceEnum.class, cashierResVO.getOrderSource()).name().toLowerCase());
        cashierResVO.setOrderName(orders.get(0).getOrderName());
        final Set<String> batchIds = orders.stream().filter(o -> !StringUtils.isEmpty(o.getBatchId())).map(Order::getBatchId).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(batchIds) && batchIds.size() > 1) {
            // 有多个批次id，或者不带批次号的订单与带批次号的订单同时支付
            BusinessErrorInfoEnum.ORDER_PAY_NOT_ALLOW_MERGE_ERROR.assertFail();
        }

        if (CollectionUtils.isNotEmpty(batchIds) && batchIds.size() == 1) {
            // 只有一个批次号
            OrderBatch orderBatch = orderBatchService.findByBatchId(orders.get(0).getBatchId());
            cashierResVO.setOrderName(orderBatch.getOrderName());
        }

        // 某些场景需要在收银台上判断判断客户端版本是否支持ums支付
        cashierVO.setOrderIds(orders.stream().map(Order::getOrderId).toArray(String[]::new));
        String payChannel = super.checkPayChannelByClientVersion(cashierVO, conditions, orders, orders.get(0).getPayChannel());
        cashierResVO.setPayChannel(payChannel);

        LocalDateTime payExpiryTime = orders.stream().map(Order::getPayExpiryTime).min(Comparator.comparing(date -> date.toEpochSecond(ZoneOffset.of("+8")))).get();
        cashierResVO.setPayLeftTime(payExpiryTime.toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));

        long discount = coupons.stream().mapToInt(Coupon::getDiscount).sum();
        cashierResVO.setDiscount(new BigDecimal(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
        long payAmount = orders.stream().mapToInt(Order::getPayAmount).sum();
        cashierResVO.setPayAmount(new BigDecimal(payAmount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
        long orderAmount = orders.stream().mapToInt(Order::getOrderAmount).sum();
        cashierResVO.setOrderAmount(new BigDecimal(orderAmount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));

        orders.forEach(order -> cashierResVO.getOrders().add(new OpenCashierResVO.Order(order.getOrderId(), order.getOrderName(), new BigDecimal(order.getOrderAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP), new BigDecimal(order.getPayAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP))));

        // 到支付中心根据支付渠道获取支付方式
        final List<PaymentPayWayQueryResponse> channels = this.getPayWayListByPayChannel(orders.get(0).getOrgPaymentChannelId(),
                orders.get(0).getPayChannel(), null, null);

        cashierResVO.setPaymentWays(channels);
        long platformDiscount = orders.stream().mapToInt(Order::getPlatformDiscount).sum();
        cashierResVO.setNotPay(0);
        if (platformDiscount == orderAmount && payAmount == 0) {
            // 优惠全额抵扣了，不需要支付
            // 直接调用支付接口
            final OrderPayParallelVO orderPayParallelVO = new OrderPayParallelVO();
            orderPayParallelVO.setOrderIds(orders.stream().map(Order::getOrderId).toArray(String[]::new));
            orderPayParallelVO.setUid(cashierParallelVO.getUid());
            orderPayParallelVO.setPayWay(PaymentWayEnum.WECHAT.getCode());
            orderPayParallelVO.setPayScene(PaySceneEnum.APP.getCode());
            this.pay(orderPayParallelVO);
            cashierResVO.setNotPay(1);
        }

        return ObjectConvertUtil.convert(cashierResVO, OpenCashierResVO.class).get();
    }

    @Override
    public OrderPrePayParallelResVO pay(OrderPayParallelVO orderPayParallelVO) {
        OpenOrderPayVO orderPayVO = ObjectConvertUtil.convert(orderPayParallelVO, OpenOrderPayVO.class).get();
        // 校验订单，不允许批次订单和其他订单一起付款
        final long batchCount = Arrays.stream(orderPayParallelVO.getOrderIds()).filter(o -> o.startsWith("B")).distinct().count();
        BusinessErrorInfoEnum.ORDER_CASHIER_MERGE_NOT_SUPPORT.assertIsTrue(orderPayParallelVO.getOrderIds().length == 1 && batchCount == 1);
        String uid = orderPayParallelVO.getUid();
        QueryWrapper<Order> conditions = new QueryWrapper<>();
        conditions.eq("uid", uid);
        conditions.eq("batch_id", orderPayParallelVO.getOrderIds()[0]);
        conditions.eq("order_status", OrderStatusEnum.waiting_pay.getCode());
        conditions.eq("pay_status", OrderPayStatusEnum.un_pay.getCode());
        List<Order> orders = orderMapper.selectList(conditions);
        // 检查订单状态、订单支付失效时间、订单禁止跨渠道支付
        BusinessErrorInfoEnum.ORDER_PAY_STATUS.assertIsTrue(orders.size() > 0);
        //BusinessErrorInfoEnum.ORDER_PAY_USER_ERROR.assertEquals(orders.get(0).getUid(),uid);
        String payChannel = orders.get(0).getPayChannel();
        if (StringUtils.isEmpty(uid) || uid.equals("0")) {
            uid = orders.get(0).getUid();
        }
        if (!StringUtils.isEmpty(uid) && !uid.equals(orders.get(0).getUid())) {
            log.warn("创建订单与支付不是同一个人，创建订单：{},支付：{}", orders.get(0).getUid(), uid);
        }
        for (Order order : orders) {
            BusinessErrorInfoEnum.ORDER_PAY_NOT_ALLOW_ORDER_TYPE_ERROR.assertIsTrue(!OrderTypeEnum.deduction.getCode().equalsIgnoreCase(order.getOrderType()));
            BusinessErrorInfoEnum.ORDER_PAY_CHANNEL.assertIsTrue(payChannel.equals(order.getPayChannel()));
            BusinessErrorInfoEnum.ORDER_PAY_TIMEOUT_CLOSE.assertIsTrue(order.getPayExpiryTime().isAfter(LocalDateTime.now()));
        }

        // 订单批次与订单明细数量校验
        OrderBatch orderBatch = orderBatchService.findByBatchId(orders.get(0).getBatchId());
        BusinessErrorInfoEnum.ORDER_PAY_NOT_ALLOW_PART_MERGE_ERROR.assertIsTrue(null != orderBatch && orderBatch.getOrderCnt() == orders.size());

        // 组织返回的支付信息
        OrderPrePayResVO prePayResVO = new OrderPrePayResVO();
        prePayResVO.setPayWay(orderPayParallelVO.getPayWay());
        prePayResVO.setOpenid(orderPayParallelVO.getOpenid());
        if (null != orderBatch) {
            prePayResVO.setOrderName(orderBatch.getOrderName());
        } else {
            prePayResVO.setOrderName(orders.get(0).getOrderName());
        }
        prePayResVO.setPayChannel(orders.get(0).getPayChannel());

        LocalDateTime payExpiryTime = orders.stream().map(Order::getPayExpiryTime).min(Comparator.comparing(date -> date.toEpochSecond(ZoneOffset.of("+8")))).get();
        prePayResVO.setPayLeftTime(payExpiryTime.toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));

        long discount = orders.stream().mapToInt(Order::getPlatformDiscount).sum();//平台优惠金额
        prePayResVO.setDiscount(new BigDecimal(discount).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP));
        long payAmount = orders.stream().mapToInt(Order::getPayAmount).sum();
        prePayResVO.setPayAmount(new BigDecimal(payAmount).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP));
        long orderAmount = orders.stream().mapToInt(Order::getOrderAmount).sum();
        prePayResVO.setOrderAmount(new BigDecimal(orderAmount).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP));

        List<OrderPrePayResVO.Order> resOrders = orders.stream().map(order -> new OrderPrePayResVO.Order(order.getOrderId(), order.getOrderName(), new BigDecimal(order.getOrderAmount()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP), new BigDecimal(order.getPayAmount()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP), EnumUtil.getEnumByCode(OrderSourceEnum.class, order.getOrderSource()).name().toLowerCase())).collect(Collectors.toList());
        prePayResVO.setOrders(resOrders);

        // 租车系统兼容字段
        OrderSourceEnum orderSourceEnum = EnumUtil.getEnumByCode(OrderSourceEnum.class, orders.get(0).getOrderSource());
        // 针对该用户加锁，5秒后自动释放，并发等待5秒获取，5秒后未获取抛出超时异常
        try (Lock lock = new Lock(Lock.LOCK_ORDER_PAY, orders.get(0).getUid(), 5, 5)) {
            BusinessErrorInfoEnum.ORDER_PAY_TIMEOUT.assertIsTrue(lock.getLockRes());

            // 判断并返回缓存中的预支付信息,缓存时长为半小时
            Optional<String> prePayInfo = this.getCachePrePayInfo(orders.get(0).getUid(), orderPayVO, orders, null == orderBatch ? null : orderBatch.getBatchId());
            if (prePayInfo.isPresent()) {
                // 判断是否为银联+支付宝+app支付，需要重新生成一笔支付流水,坑爹
                if (orders.get(0).getPayChannel().equalsIgnoreCase(PaymentChannelEnum.UMS.getCode()) && orderPayVO.getPayScene().equalsIgnoreCase(PaySceneEnum.APP.getCode()) && orderPayVO.getPayWay().equalsIgnoreCase(PaymentWayEnum.ALIPAY.getCode())) {
                    log.info("作废支付宝缓存预支付信息，重新生成流水");
                    // TODO 调用支付中心查询该流水的支付结果，已支付则抛出异常
                    String cacheUniqueKey = orderIdHelper.getUniqueIdByOrderIds(orders.get(0).getUid(), orders.stream().map(Order::getOrderId).collect(Collectors.toList()), orderPayVO.getPayWay(), orderPayVO.getPayScene(), null == orderBatch ? null : orderBatch.getBatchId());
                    OrderCache.delPrePayInfo(cacheUniqueKey);

                } else {
                    if (discount == orderAmount && payAmount == 0) {
                        // 优惠全额抵扣了，不需要支付
                        prePayResVO.setPayInfo("not_pay");
                    } else {
                        // 正常支付订单，需要支付
                        prePayResVO.setPayInfo(prePayInfo.get());
                    }
                    return this.OrderPrePayResVO2OrderPrePayParallelResVO(prePayResVO);
                }
            }

            // 查询订单的优惠券信息
            List<Coupon> coupons = couponService.find(orders.stream().map(Order::getOrderId).toArray(String[]::new));
            String spInfo = coupons.stream().map(coupon -> coupon.getCouponCode() + "," + coupon.getCouponName()).collect(Collectors.toList()).stream().collect(Collectors.joining(","));

            String orderSource = orders.size() > 1 ? OrderSourceEnum.CONSOLIDATION.getCode() : orders.get(0).getOrderSource();
            String paymentId = orderIdHelper.getPaymentId(uid);
            OrderPayDTO orderPayDTO = new OrderPayDTO();
            orderPayDTO.setUid(uid);
            orderPayDTO.setOrderName(orders.get(0).getOrderName());
            orderPayDTO.setUserTel(orders.get(0).getMobile());
            orderPayDTO.setPaymentId(paymentId);
            orderPayDTO.setBusinessType(orderSource);
            orderPayDTO.setTs(LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
            orderPayDTO.setAmount(orders.stream().mapToInt(Order::getPayAmount).sum());
            orderPayDTO.setOrderAmount(orders.stream().mapToInt(Order::getOrderAmount).sum());
            orderPayDTO.setDiscount(orders.stream().mapToInt(Order::getPlatformDiscount).sum());
            orderPayDTO.setPayScene(orderPayVO.getPayScene());
            orderPayDTO.setPayChannel(payChannel);
            orderPayDTO.setPayWay(orderPayVO.getPayWay());
            orderPayDTO.setSpInfo(spInfo);
            orderPayDTO.setOpenid(orderPayVO.getOpenid());
            orderPayDTO.setSubAppId(orderPayVO.getSubAppId());
            orderPayDTO.setReturnUrl(orderPayVO.getReturnUrl());
            // 如果是小程序支付，没有传openid，默认使用创建订单时的openid
            if (OrderSceneEnum.miniapp.name().equals(orderPayVO.getPayScene()) && StringUtils.isEmpty(orderPayVO.getOpenid())) {
                orderPayDTO.setOpenid(orders.get(0).getOpenid());
            }
            // 如果支付方式是微信，判断是否有goods_tag
            if (PaymentWayEnum.WECHAT.getCode().equals(orderPayVO.getPayWay()) && !StringUtils.isEmpty(orders.get(0).getGoodsTag())) {
                orderPayDTO.setGoodsTag(orders.get(0).getGoodsTag());
            }
            // 设置主商户
            orderPayDTO.setMerchantCode(orderCenterConfigProperties.getMainMerchant().getCode());
            orderPayDTO.setMerchantName(orderCenterConfigProperties.getMainMerchant().getName());
            // 组织子订单数据
            List<SubOrder> subOrders = subOrderService.find(orders.stream().map(Order::getOrderId).toArray(String[]::new));
            List<OrderPayDTO.SubOrderPayDTO> subOrderPayDTOS = subOrders.stream().map(subOrder -> {
                OrderPayDTO.SubOrderPayDTO subOrderPayDTO = ObjectConvertUtil.convert(subOrder, OrderPayDTO.SubOrderPayDTO.class).get();
                subOrderPayDTO.setOrderAmount(subOrder.getSubOrderAmount());
                subOrderPayDTO.setAmount(subOrder.getPayAmount());
                return subOrderPayDTO;
            }).collect(Collectors.toList());
            orderPayDTO.setSubOrderPayDTOS(subOrderPayDTOS);

            // 调用支付中心服务
            Result<UnifiedOrderResVO> result;
            if (discount == orderAmount && payAmount == 0 && CollectionUtils.isNotEmpty(coupons)) {
                // 优惠全额抵扣了，不需要支付
                orderPayDTO.setDiscount(0);
                orderPayDTO.setPayWay(PaymentWayEnum.DEDUCTION.getCode());
                orderPayDTO.setPayScene(PaySceneEnum.DEDUCTION.getCode());
                result = paymentFeignService.deductionUnified(orderPayDTO);
            } else {
                // 正常支付订单，需要支付
                result = paymentFeignService.unifiedOrder(orderPayDTO);
            }

            log.info("支付单号:{},支付返回信息:{}", paymentId, result);
            BusinessErrorInfoEnum.ORDER_PAY_SERVER_ERROR.assertIsTrue(result.getRet() == 0 && result.getErrorCode() == 0, StringUtils.isNotEmpty(result.getMsg()) ? result.getMsg() : "");

            // 缓存预支付信息
//            TreeSet<String> orderIds = Stream.of(orderPayVO.getOrderIds()).collect(Collectors.toCollection(TreeSet::new));
            String cacheUniqueKey = orderIdHelper.getUniqueIdByOrderIds(orders.get(0).getUid(), orders.stream().map(Order::getOrderId).collect(Collectors.toList()), orderPayVO.getPayWay(), orderPayVO.getPayScene(), null == orderBatch ? null : orderBatch.getBatchId());
            log.info("支付订单，获取cacheUniqueKey：{}", cacheUniqueKey);
            OrderCache.prePayInfo(cacheUniqueKey, result.getData());

            // 缓存提交的支付参数
            PayParamsCacheBO payParamsCacheBO = ObjectConvertUtil.convert(orderPayVO, PayParamsCacheBO.class).get();
            payParamsCacheBO.setOrderIds(orders.stream().map(Order::getOrderId).toArray(String[]::new));
            payParamsCacheBO.setUid(orders.get(0).getUid());
            OrderCache.payParams(paymentId, payParamsCacheBO);

            List<Order> collect = orders.stream().filter(o -> StringUtils.isEmpty(o.getOrderChannel())).collect(Collectors.toList());
            if (discount == orderAmount && payAmount == 0 && CollectionUtils.isNotEmpty(coupons)) {
                // 优惠全额抵扣了，不需要支付
                prePayResVO.setPayInfo("not_pay");
            } else {
                // 正常支付订单，需要支付
                prePayResVO.setPayInfo(result.getData().getPayInfo());
            }

            return this.OrderPrePayResVO2OrderPrePayParallelResVO(prePayResVO);
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            throw new BusinessException(BusinessErrorInfoEnum.ORDER_PAY_TIMEOUT, null, e.getMessage(), e);
        }
    }

    @Override
    public Map<String, String> getPayStatus(String uid, String batchId) {
        final OrderBatch batch = orderBatchService.findByBatchId(batchId);
        Map<String, String> res = new HashMap<>();
        res.put("pay_status", batch.getPayStatus());
        return res;
    }

    private OrderPrePayParallelResVO OrderPrePayResVO2OrderPrePayParallelResVO(OrderPrePayResVO prePayResVO) {

        OrderPrePayParallelResVO resVO = ObjectConvertUtil.convert(prePayResVO, OrderPrePayParallelResVO.class).get();
        if (prePayResVO.getOrders().size() > 1) {
            resVO.setIsMergeOrder(1);
        } else {
            resVO.setType(prePayResVO.getOrders().get(0).getOrderSource());
            resVO.setOrderId(prePayResVO.getOrders().get(0).getOrderId());
        }
        return resVO;
    }
}
